import sys, time, serial, serial.tools.list_ports
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox
from PyQt5.QtCore import QThread, pyqtSignal
from ui_serial import Ui_MainWindow   # 刚才生成的文件

class SerialThread(QThread):
    data_ready = pyqtSignal(str)       # 给主线程回传字符串

    def __init__(self, ser):
        super().__init__()
        self.ser = ser
        self.working = True

    def run(self):
        while self.working:
            try:
                if self.ser.in_waiting:
                    text = self.ser.read(self.ser.in_waiting).decode(errors='ignore')
                    self.data_ready.emit(text)
            except Exception as e:
                self.data_ready.emit(f'<接收错误：{e}>')
            time.sleep(0.01)

class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.setWindowTitle('PyQt5 串口助手')
        self.ser = None          # serial.Serial 对象
        self.recv_thread = None  # 接收线程

        # 绑定信号
        self.btnOpen.clicked.connect(self.on_open)
        self.btnSend.clicked.connect(self.on_send)
        self.btnClear.clicked.connect(self.txtRecv.clear)
        self.btnSave.clicked.connect(self.on_save)
        self.refresh_port()
        self.show()

    # ---------- 串口扫描 ----------
    def refresh_port(self):
        self.cmbPort.clear()
        ports = [p.device for p in serial.tools.list_ports.comports()]
        self.cmbPort.addItems(ports)

    # ---------- 打开/关闭 ----------
    def on_open(self):
        if self.ser and self.ser.is_open:
            self.close_serial()
        else:
            try:
                port = self.cmbPort.currentText()
                baud = int(self.cmbBaud.currentText())
                self.ser = serial.Serial(port, baud, timeout=0.1)
                self.recv_thread = SerialThread(self.ser)
                self.recv_thread.data_ready.connect(self.append_recv)
                self.recv_thread.start()
                self.btnOpen.setText('关闭')
                self.statusBar().showMessage(f'{port} 已打开')
            except Exception as e:
                QMessageBox.critical(self, '错误', str(e))

    def close_serial(self):
        if self.recv_thread:
            self.recv_thread.working = False
            self.recv_thread.wait()
        if self.ser and self.ser.is_open:
            self.ser.close()
        self.btnOpen.setText('打开')
        self.statusBar().showMessage('串口已关闭')

    # ---------- 发送 ----------
    def on_send(self):
        if not self.ser or not self.ser.is_open:
            QMessageBox.warning(self, '提示', '请先打开串口')
            return
        text = self.txtSend.text()
        if not text:
            return
        try:
            self.ser.write(text.encode())
        except Exception as e:
            QMessageBox.critical(self, '发送错误', str(e))

    # ---------- 接收显示 ----------
    def append_recv(self, text):
        self.txtRecv.moveCursor(self.txtRecv.textCursor().End)
        self.txtRecv.insertPlainText(text)

    # ---------- 保存日志 ----------
    def on_save(self):
        path, _ = QFileDialog.getSaveFileName(self, '保存日志', time.strftime('%Y%m%d_%H%M%S.txt'))
        if path:
            with open(path, 'w', encoding='utf-8') as f:
                f.write(self.txtRecv.toPlainText())
            self.statusBar().showMessage(f'已保存 {path}')

    # ---------- 退出清理 ----------
    def closeEvent(self, event):
        self.close_serial()
        event.accept()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MainWindow()
    sys.exit(app.exec_())